package serverHandling;

import general.ActionEnum;
import general.FileUtils;
import general.GlobalProperties;
import general.SerwerURL;

import java.awt.TrayIcon;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import jsonObjects.Attributes;
import jsonObjects.Files;
import jsonObjects.Login;
import jsonObjects.Profile;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIUtils;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;

import systemFile.FileBuffer;
import systemFile.FileWatcher;
import systemFile.SysTray;

import com.google.gson.Gson;

public class HttpRequestHandlers {
    private static Logger log = Logger.getLogger(HttpRequestHandlers.class);

    static DefaultHttpClient httpclient = new DefaultHttpClient();
    static Gson gson = new Gson();

    @SuppressWarnings("unchecked")
    public static void initialSynchronization() throws IOException,
	    URISyntaxException {
	try {
	    if (autoryzacjaByLogin()) {
		GlobalProperties.isLogged = true;
		getProfiles();
		ArrayList<Files> tmpFilesList;

		/*
		 * Profil -> lista plików z nim powiązanych
		 */
		for (Profile prof : SessionObjects.getProfiles()) {

		    // lista plikow lokalnych
		    File dir = new File(GlobalProperties.getFolderMonit()
			    + prof.getName());
		    ArrayList<Files> localFiles = null;

		    if (dir.exists()) {
			File[] files = dir.listFiles();
			localFiles = new ArrayList<Files>();

			// plik .properties pomijany, wysylany dopiero na koniec
			// synchronizacji
			if (files != null)
			    for (File tmp : files)
				if (!FileUtils.getExtension(tmp).equals(
					"properties"))
				    localFiles.add(new Files(tmp.getName(),
					    new Date(tmp.lastModified())));
		    }

		    tmpFilesList = getListProfile(prof.getId());

		    SessionObjects.addFilesToProfile(prof.getName(),
			    (ArrayList<Files>) tmpFilesList.clone());

		    algorystSynchronizacjiWstepnej(tmpFilesList, localFiles,
			    prof);

		    // wysylanie pliku properties
		    if (FileUtils.getProfilePropertyFile(prof).exists())
			FileBuffer
				.getInstance()
				.getListOfFile()
				.put(FileUtils.getProfilePropertyFile(prof),
					ActionEnum.EDIT);
		}
	    } else {
		FileWatcher.icon.displayMessage("", "Bledny login/haslo!",
			TrayIcon.MessageType.ERROR);
		FileWatcher.icon.setImage(SysTray.getErrorImage());
	    }
	} catch (IOException e) {
	    log.error(e.getMessage());
	} catch (URISyntaxException e) {
	    log.error(e.getMessage());
	}
    }

    public static void algorystSynchronizacjiWstepnej(
	    ArrayList<Files> remoteFiles, ArrayList<Files> localFiles,
	    Profile profile) {

	File toDelete = new File(GlobalProperties.getFolderMonit()
		+ profile.getName() + "\\toDelete");
	FileUtils.deleteFileUnsafe(toDelete);

	if (localFiles != null && remoteFiles != null) {
	    List<Files> delete;
	    List<Files> add;
	    // Pliki nowsze na dysku lokalnym niz na serwerze
	    List<Files> editLocal = new ArrayList<Files>();
	    // Pliki ktore sa nowsze na serwerze niz dysku lokalnym
	    List<Files> editRemote = new ArrayList<Files>();
	    int comp;

	    for (Iterator it = localFiles.iterator(); it.hasNext();) {
		Files local = (Files) it.next();
		for (Iterator itInc = remoteFiles.iterator(); itInc.hasNext();) {
		    Files inc = (Files) itInc.next();
		    // sprawdzanie nazwy pliku
		    if (local.getName().equals(inc.getName())) {
			/*
			 * sprawdzanie czasu > 0 -> inc wieksza < 0 local
			 * wieksza 0 takie same
			 */
			comp = inc.getCreated().compareTo(local.getCreated());

			if (comp > 0) {
			    editRemote.add(inc);
			} else if (comp < 0) {
			    editLocal.add(local);
			} else {
			    // nie trzeba nic z plikiem robic
			}

			try {
			    itInc.remove();
			    it.remove();
			} catch (Exception e) {
			    log.error(e.getMessage());
			}
		    }
		}
	    }
	    /*
	     * to co zostaje w local -> do usuniecia incoming -> do dodania
	     */
	    delete = localFiles;
	    add = remoteFiles;
	    String profilePath = GlobalProperties.getFolderMonit()
		    + profile.getName() + "\\";

	    try {
		for (Files tmp : add) {
		    if (!tmp.getName().contains(".properties")) {
			FileUtils.deleteFileUnsafe(toDelete);
			download(tmp, profilePath);
			FileUtils.dopiszZmianyDoProperties(tmp, profile,
				tmp.getAttributes());
		    }
		}
		for (Files tmp : editRemote) {
		    if (!tmp.getName().contains(".properties")) {
			FileUtils.deleteFileUnsafe(toDelete);
			download(tmp, profilePath);
			FileUtils.dopiszZmianyDoProperties(tmp, profile,
				tmp.getAttributes());
		    }
		}
	    } catch (IOException e) {
		e.printStackTrace();
	    } catch (URISyntaxException e) {
		e.printStackTrace();
	    } catch (InterruptedException e) {
		e.printStackTrace();
	    }
	    for (Files tmp : editLocal) {
		FileBuffer
			.getInstance()
			.getListOfFile()
			.put(new File(profilePath + tmp.getName().trim()),
				ActionEnum.EDIT);
		// jezeli plik jest nowy na dysku lokalnym, nie ma potrzeby
		// modyfikacji *.properties
	    }
	    for (Files delFile : delete) {
		FileUtils.deleteFileSafe(new File(profilePath
			+ delFile.getName().trim()));
		try {
		    FileUtils.deleteAttributeFromFile(delFile, profile);
		} catch (InterruptedException e) {
		    e.printStackTrace();
		    log.error(e.getMessage());
		}
	    }

	} else {
	    if (localFiles == null && !remoteFiles.isEmpty()) {
		log.error("Error : both lists are empty in synchronization algorithm. Profile "
			+ profile.getName());
		return;
	    }
	    // jest profil lokalnie a na serwerze nie ma.
	    // taka sytulacja nie powinna wystapic...
	    if (localFiles != null && !localFiles.isEmpty()
		    && remoteFiles.isEmpty()) {
		log.error("Error : Profile " + profile.getName()
			+ " exist on local filesystem but doesn't on server.");
		return;
	    }
	    // jest profil na serwrze a lokalnie nie ma.
	    if (localFiles == null && !remoteFiles.isEmpty()) {
		String profilePath = GlobalProperties.getFolderMonit()
			+ profile.getName() + "\\";
		try {
		    for (Files tmp : remoteFiles) {
			download(tmp, profilePath);
		    }
		} catch (IOException e) {
		    e.printStackTrace();
		} catch (URISyntaxException e) {
		    e.printStackTrace();
		}
	    }
	}
    }

    public static void tryToSendFile(File file, ActionEnum action) {
	try {
	    upload(file, action);
	} catch (IOException e) {
	    log.error(e.getMessage());
	} catch (URISyntaxException e) {
	    log.error(e.getMessage());
	}

    }

    @SuppressWarnings("InstantiatingObjectToGetClassObject")
    public static boolean autoryzacjaByLogin() throws IOException,
	    URISyntaxException {
	String param = "login=" + GlobalProperties.getUsername() + "&password="
		+ GlobalProperties.getPassword();
	URI uri = URIUtils.createURI(SerwerURL.PROTOCOL,
		SerwerURL.getHostPort(), -1, SerwerURL.LOGIN, param, null);
	HttpGet httpget = new HttpGet(uri);

	HttpResponse response = httpclient.execute(httpget);
	HttpEntity entity = response.getEntity();
	String entityContents = EntityUtils.toString(entity);

	SessionObjects.setCookies(httpclient.getCookieStore().getCookies());

	Login jsonObj = gson.fromJson(entityContents, Login.class);

	/*
	 * jezeli nie ma komunikatu o blednym hasle
	 */
	if (jsonObj.getWrongPassword() == null) {
	    String session_key = jsonObj.getSession_key();
	    String user = jsonObj.getUser();

	    Properties tmp = GlobalProperties.properties;
	    tmp.put(GlobalProperties.SESSION_KEY, session_key);
	    tmp.put(GlobalProperties.USER, user);

	    FileOutputStream fos = new FileOutputStream("global.properties");

	    tmp.store(fos, "");
	    return true;
	} else
	    return false;
    }

    public static void logout() throws IOException, URISyntaxException {
	URI uri = URIUtils.createURI(SerwerURL.PROTOCOL,
		SerwerURL.getHostPort(), -1, SerwerURL.LOGOUT, null, null);
	HttpGet httpget = new HttpGet(uri);
	httpclient.getCookieStore().addCookie(
		SessionObjects.getCookies().get(0));

	httpclient.execute(httpget);
    }

    @Deprecated
    public static void deleteFile(String filePath, ActionEnum action)
	    throws IOException, URISyntaxException {
	URI uri = URIUtils.createURI(SerwerURL.PROTOCOL,
		SerwerURL.getHostPort(), -1, SerwerURL.TRANSMISION_SHORT, null,
		null);
	HttpPost method = new HttpPost(uri);
	MultipartEntity entity = new MultipartEntity();

	entity.addPart("action",
		new StringBody(action.getOpis(), Charset.forName("UTF-8")));
	entity.addPart("root",
		new StringBody(filePath, Charset.forName("UTF-8")));
	method.setEntity(entity);

	httpclient.execute(method);
    }

    @SuppressWarnings({ "InstantiatingObjectToGetClassObject",
	    "ConstantConditions" })
    public static void getProfiles() throws IOException, URISyntaxException {
	String profile = "";
	FileOutputStream fos = null;
	try {
	    URI uri = URIUtils.createURI(SerwerURL.PROTOCOL,
		    SerwerURL.getHostPort(), -1, SerwerURL.GET_PROFILES, null,
		    null);

	    httpclient.getCookieStore().addCookie(
		    SessionObjects.getCookies().get(0));

	    HttpGet httpget = new HttpGet(uri);
	    HttpResponse response = httpclient.execute(httpget);

	    String entityContents = EntityUtils.toString(response.getEntity());

	    SessionObjects.setProfiles(Arrays.asList(gson.fromJson(
		    entityContents, Profile[].class)));

	    if (SessionObjects.getProfiles().size() != 0)
		for (Profile profil : SessionObjects.getProfiles())
		    // nazwa_profilu?id*
		    // ? i * sa zakazane w OSach do nazw folderów
		    profile += new String(profil.getName().getBytes("UTF-8"),
			    "UTF-8") + "?" + profil.getId() + "*";

	    Properties tmp = GlobalProperties.properties;
	    tmp.put(GlobalProperties.PROFILES, profile);

	    fos = new FileOutputStream("global.properties");
	    tmp.store(fos, "");

	} catch (Exception e) {
	    log.error(e.getMessage());
	} finally {
	    fos.close();
	}
    }

    public static ArrayList<Files> getListProfile(String profileID)
	    throws IOException, URISyntaxException {
	Files[] filesList = new Files[0];
	try {
	    String param = "profile_id=" + profileID;
	    URI uri = URIUtils.createURI(SerwerURL.PROTOCOL,
		    SerwerURL.getHostPort(), -1, SerwerURL.LIST_PROFILES,
		    param, null);
	    HttpClient httpclient = new DefaultHttpClient();
	    HttpGet httpget = new HttpGet(uri);

	    HttpResponse response = httpclient.execute(httpget);

	    HttpEntity entity = response.getEntity();
	    String entityContents = EntityUtils.toString(entity);
	    filesList = gson.fromJson(entityContents, Files[].class);

	} catch (Exception e) {
	    log.error(e.getMessage());
	}
	return new ArrayList<Files>(Arrays.asList(filesList));
    }

    public static void upload(File file, ActionEnum action) throws IOException,
	    URISyntaxException {
	// GIVEN
	URI uri = URIUtils.createURI(SerwerURL.PROTOCOL,
		SerwerURL.getHostPort(), -1, SerwerURL.UPLOAD, null, null);
	DefaultHttpClient httpclientSend = new DefaultHttpClient();
	HttpPost method = new HttpPost(uri);
	MultipartEntity entity = new MultipartEntity();

	if (httpclientSend.getCookieStore().getCookies().isEmpty())
	    httpclientSend.getCookieStore().addCookie(
		    SessionObjects.getCookies().get(0));

	// WHEN
	entity.addPart("action",
		new StringBody(action.getOpis(), Charset.forName("UTF-8")));
	entity.addPart(
		"profile_id",
		new StringBody(GlobalProperties.getProfileId(FileUtils
			.getProperFileUrl(file.getCanonicalPath()).replace("/",
				""))));

	String rootFolderName = file.getParentFile().getName();
	Map<Attributes, String> attributesMap = FileUtils.getAtrybutyPliku(
		file.getName(), FileUtils.getProfileByName(rootFolderName));
	if (!attributesMap.isEmpty())
	    for (Attributes a : attributesMap.keySet())
		entity.addPart("attr_" + a.getId(), new StringBody(
			attributesMap.get(a)));

	entity.addPart(
		"profileName",
		new StringBody(FileUtils.getProperFileUrl(file
			.getCanonicalPath()), Charset.forName("UTF-8")));

	FileBody fileBody = new FileBody(file);
	entity.addPart("file", fileBody);
	method.setEntity(entity);

	/*
	 * RESPONSE
	 */
	HttpResponse response = httpclientSend.execute(method);

	System.out.println(response.getStatusLine());
    }

    public static void download(Files fileID, String filePath) throws IOException, URISyntaxException {
	try {
	    String param = "file_id=" + fileID.getId();
	    URI uri = URIUtils.createURI(SerwerURL.PROTOCOL,
		    SerwerURL.getHostPort(), -1, SerwerURL.DOWNLOAD_BODY,
		    param, null);
	    DefaultHttpClient httpclientSend = new DefaultHttpClient();
	    HttpPost method = new HttpPost(uri);
	    httpclientSend.getCookieStore().addCookie(
		    SessionObjects.getCookies().get(0));

	    /*
	     * RESPONSE
	     */
	    HttpResponse response = httpclientSend.execute(method);
	    HttpEntity entity = response.getEntity();

	    if (FileUtils.renameFile(filePath + fileID.getName().trim(),
		    filePath + "toDelete")) {
		// file save
		BufferedOutputStream bos = null;
		try {
		    byte buf[] = EntityUtils.toByteArray(entity);
		    FileOutputStream fos = new FileOutputStream(new File(
			    filePath + fileID.getName().trim()));
		    bos = new BufferedOutputStream(fos);
		    bos.write(buf);
		} finally {
		    if (bos != null) {
			try {
			    // flush the BufferedOutputStream
			    bos.flush();
			    // close the BufferedOutputStream
			    bos.close();
			} catch (Exception e) {
			    e.printStackTrace();
			}
		    }
		}
		File toDelete = new File(filePath + "toDelete");
		FileUtils.deleteFileUnsafe(toDelete);
	    }
	} catch (Exception e) {
	    log.error(e.getMessage());
	    e.printStackTrace();
	}
    }

    public static Files[] versioning(String idx) {
	try {
	    String param = "file_id=" + idx;
	    URI uri = URIUtils.createURI(SerwerURL.PROTOCOL,
		    SerwerURL.getHostPort(), -1, SerwerURL.VERSIONING, param,
		    null);
	    DefaultHttpClient httpclientSend = new DefaultHttpClient();
	    HttpPost method = new HttpPost(uri);
	    httpclientSend.getCookieStore().addCookie(
		    SessionObjects.getCookies().get(0));

	    /*
	     * RESPONSE
	     */
	    HttpResponse response = httpclientSend.execute(method);
	    String entityContents = EntityUtils.toString(response.getEntity());

	    return gson.fromJson(entityContents, Files[].class);
	} catch (Exception e) {
	    log.error(e.getMessage());
	    e.printStackTrace();
	}
	return null;
    }

    public static void writeToFile(InputStream is, File file) {
	try {
	    DataOutputStream out = new DataOutputStream(
		    new BufferedOutputStream(new FileOutputStream(file)));
	    int c;
	    while ((c = is.read()) != -1) {
		out.writeByte(c);
	    }
	    is.close();
	    out.close();
	} catch (IOException e) {
	    System.err.println("Error Writing/Reading Streams.");
	}
    }

}
